package com.lagou.session.impl;

import com.lagou.config.BoundSql;
import com.lagou.pojo.Configuration;
import com.lagou.pojo.ErrorContext;
import com.lagou.pojo.MappedStatement;
import com.lagou.session.Executor;
import com.lagou.utils.ParameterMapping;
import org.apache.log4j.Logger;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@SuppressWarnings("unchecked")
public class SimpleExecutor implements Executor {

    private Logger logger = Logger.getLogger(SimpleExecutor.class);
    private Connection connection;
    private PreparedStatement preparedStatement;
    private ResultSet resultSet;
    /**
     * 一级缓存
     */
    protected Map<Object, Object> sessionCache = new HashMap<Object, Object>();

    @Override
    public <T> List<T> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        String cacheKey = getCacheKey(mappedStatement, params);
        // 查询二级缓存
        List<T> resultList;
        if (configuration.getCacheEnabled() && !mappedStatement.getUseCache()) {
            resultList = (List<T>)configuration.getStatementCache().get(mappedStatement.getNamespace()).get(cacheKey);
            if (resultList != null) {
                logger.info("hit twoLevelCache!!!");
                return resultList;
            }
        }
        // 查询一级缓存
        resultList = (ArrayList<T>) sessionCache.get(cacheKey);
        if (resultList != null && resultList.size() > 0) {
            logger.info("hit oneLevelCache!!!");
            return resultList;
        }
        // 查询数据库
        try {
            // 1. 获取数据库连接
            connection = configuration.getDataSource().getConnection();
            // 2. 对sql进⾏转换
            String sql = mappedStatement.getSql();
            BoundSql boundsql = BoundSql.getBoundSql(sql);
            String sqlText = boundsql.getSqlText();
            logger.info("sql :" + sqlText);
            ErrorContext.instance().sql(sqlText);
            List<ParameterMapping> parameterMappings = boundsql.getParameterMappings();
            Class<?> paramterType = mappedStatement.getParamterType();// 获取传⼊参数类型
            // 3. 获取预编译对象, 并设置参数
            preparedStatement = connection.prepareStatement(sqlText);
            for (int i = 0; i < parameterMappings.size(); i++) {
                ParameterMapping parameterMapping = parameterMappings.get(i);
                String columnName = parameterMapping.getContent();
                // 反射获取字段 获取id变量,通过变量获取入参的值
                Field field = paramterType.getDeclaredField(columnName);
                field.setAccessible(true);
                Object value = field.get(params[0]);
                logger.info("param: " + columnName + " = " + value);
                // 给占位符赋值
                preparedStatement.setObject(i + 1, value);
            }
            // 4. 执行sql
            resultSet = preparedStatement.executeQuery();
            // 5. 封装返回结果集
            Class<?> resultType = mappedStatement.getResultType();  // 获取结果类型
            resultList = new ArrayList<T>();
            while (resultSet.next()) {
                T result = (T)resultType.newInstance();
                ResultSetMetaData metaData = resultSet.getMetaData();
                int columnCount = metaData.getColumnCount();
                for (int i = 1; i <= columnCount; i++) {
                    String columnName = metaData.getColumnName(i);  // 字段名
                    Object value = resultSet.getObject(columnName); // 字段值
                    // 使用内省, 创建属性描述器，为属性⽣成读写⽅法，根据数据库表和实体的对应关系，完成封装
                    PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultType);
                    Method writeMethod = propertyDescriptor.getWriteMethod();
                    writeMethod.invoke(result, value);
                }
                resultList.add(result);
            }
            return resultList;
        } finally {
            // 移除一级缓存
            sessionCache.remove(cacheKey);
            // 移除二级缓存
            if (configuration.getCacheEnabled()){
                configuration.getStatementCache().get(mappedStatement.getNamespace()).clear();
            }
            // 保存缓存
            if (resultList != null && resultList.size() != 0) {
                // 保存一级缓存
                sessionCache.put(cacheKey, resultList);
                // 保存二级缓存
                if (configuration.getCacheEnabled() && !mappedStatement.getUseCache()){
                    configuration.getStatementCache().get(mappedStatement.getNamespace()).put(cacheKey, resultList);
                }
            }
        }
    }

    @Override
    public int update(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        try {
            // 1. 获取数据库连接
            connection = configuration.getDataSource().getConnection();
            // 2. 对sql进⾏转换
            String sql = mappedStatement.getSql();
            BoundSql boundsql = BoundSql.getBoundSql(sql);
            String sqlText = boundsql.getSqlText();
            logger.info("sql :" + sqlText);
            ErrorContext.instance().sql(sqlText);
            List<ParameterMapping> parameterMappings = boundsql.getParameterMappings();
            Class<?> paramterType = mappedStatement.getParamterType();// 获取传⼊参数类型
            // 3. 获取预编译对象, 并设置参数
            preparedStatement = connection.prepareStatement(sqlText);
            for (int i = 0; i < parameterMappings.size(); i++) {
                ParameterMapping parameterMapping = parameterMappings.get(i);
                String columnName = parameterMapping.getContent();
                // 反射获取字段 获取id变量,通过变量获取入参的值
                Field field = paramterType.getDeclaredField(columnName);
                field.setAccessible(true);
                Object value = field.get(params[0]);
                logger.info("param: " + columnName + " = " + value);
                // 给占位符赋值
                preparedStatement.setObject(i + 1, value);
            }
            return preparedStatement.executeUpdate();
        } finally {
            // 清空一级缓存
            sessionCache.clear();
            // 清空二级缓存
            if (configuration.getCacheEnabled() && !mappedStatement.getUseCache()) {
                configuration.getStatementCache().get(mappedStatement.getNamespace()).clear();
            }
        }
    }

    @Override
    public void close() {
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        if (preparedStatement != null) {
            try {
                preparedStatement.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        if (connection != null) {
            try {
                connection.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 生成缓存key
     */
    private String getCacheKey(MappedStatement mappedStatement, Object... params) throws Exception {
        StringBuilder cacheKey = new StringBuilder();
        cacheKey.append(mappedStatement.getId())
                .append(mappedStatement.getSql())
                .append(mappedStatement.getParamterType())
                .append(mappedStatement.getParamterType());
        BoundSql boundsql = BoundSql.getBoundSql(mappedStatement.getSql());
        cacheKey.append(boundsql.getSqlText());
        List<ParameterMapping> parameterMappings = boundsql.getParameterMappings();
        for (ParameterMapping parameterMapping : parameterMappings) {
            cacheKey.append(parameterMapping.getContent());
        }
        for (int i = 0; i < parameterMappings.size(); i++) {
            ParameterMapping parameterMapping = parameterMappings.get(i);
            String columnName = parameterMapping.getContent();
            // 反射获取字段 获取id变量,通过变量获取入参的值
            Field field = mappedStatement.getParamterType().getDeclaredField(columnName);
            field.setAccessible(true);
            Object value = field.get(params[0]);
            cacheKey.append(value);
        }
        return cacheKey.toString();
    }
}
